<?php

declare(strict_types=1);

namespace DreamCat\StateMachine\Parser\Inner;

use DreamCat\StateMachine\Enums\RoleType;
use DreamCat\StateMachine\Enums\TransferType;
use DreamCat\StateMachine\Error\DefineConfigException;
use DreamCat\StateMachine\Factory\MachineFactory;
use DreamCat\StateMachine\Popo\Define\ImmediatelyTransfer;
use DreamCat\StateMachine\Popo\Define\MachineActionDetail;
use DreamCat\StateMachine\Popo\Define\MachineNodeDetail;
use DreamCat\StateMachine\Popo\Define\MachineRoleDetail;
use DreamCat\StateMachine\Popo\Define\SwitchResult;
use DreamCat\StateMachine\Popo\Define\SwitchTransfer;
use DreamCat\StateMachine\Popo\MachineDefine;
use DreamCat\StateMachine\StateInterface\ActionExecConditionInterface;
use DreamCat\StateMachine\StateInterface\ActionStatusQueryInterface;
use DreamCat\StateMachine\StateInterface\PreConditionInterface;
use DreamCat\StateMachine\StateInterface\TransferDecisionInterface;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Validator;
use stdClass;

/**
 * 内部使用的解析json文件的类
 * @author vijay
 */
class ParseConfig
{
    /** @var string 定义schema的目录 */
    private string $schemaDir;

    /**
     * 构造函数
     * @throws DefineConfigException
     */
    public function __construct()
    {
        $dir = __DIR__ . "/../../../resource";
        if (!is_dir($dir)) {
            throw new DefineConfigException("找不到Schema定义所在的目录"); // @codeCoverageIgnore
        }
        $dir = realpath($dir);
        if (!is_readable($dir)) {
            throw new DefineConfigException("Schema定义的目录 {$dir} 无法读取"); // @codeCoverageIgnore
        }
        $this->schemaDir = $dir;
    }

    /**
     * 获取Schema定义所在的目录
     * @return string Schema定义所在的目录
     */
    public function getSchemaDir(): string
    {
        return $this->schemaDir;
    }

    /**
     * 获取json文件内容
     * @param string $filePath 文件路径
     * @param string $fileUsed 文件用途
     * @return stdClass 解析后的结果
     * @throws DefineConfigException
     */
    public function getJsonFile(string $filePath, string $fileUsed): stdClass
    {
        if (!file_exists($filePath)) {
            throw new DefineConfigException("找不到 {$fileUsed} [{$filePath}]");
        }
        if (!is_readable($filePath)) {
            throw new DefineConfigException("{$fileUsed} 无法读取 [{$filePath}]"); // @codeCoverageIgnore
        }
        $fileContent = file_get_contents($filePath);
        if (!$fileContent) {
            throw new DefineConfigException("{$fileUsed} 内容为空 [{$filePath}]");
        }
        $result = json_decode($fileContent);
        $error = json_last_error();
        if ($error) {
            $msg = json_last_error_msg();
            throw new DefineConfigException("{$fileUsed} [{$filePath}] json解析失败 <{$error}> [{$msg}]");
        }
        return $result;
    }

    /**
     * 校验配置文件是否符合结构定义
     * @param stdClass $config 解析完成的配置文件
     * @param stdClass $schema 解析完成的schema文件
     * @param string $configDesc
     * @return void
     * @throws DefineConfigException
     */
    public function validSchema(stdClass $config, stdClass $schema, string $configDesc): void
    {
        $schema->id = $this->schemaDir;
        $validator = new Validator();
        $validator->validate(
            $config,
            $schema,
            Constraint::CHECK_MODE_COERCE_TYPES | Constraint::CHECK_MODE_APPLY_DEFAULTS
        );
        if ($validator->isValid()) {
            return;
        }
        $text = "{$configDesc} 格式不正确";
        foreach ($validator->getErrors() as $error) {
            $text .= "\n[{$error["property"]}] {$error["message"]}";
        }
        throw new DefineConfigException($text);
    }

    /**
     * 构建定义popo
     * @param stdClass $parsedConfig 解析后的配置文件
     * @return MachineDefine 构建后的对象
     * @throws DefineConfigException
     */
    public function checkParseConfig(stdClass $parsedConfig): MachineDefine
    {
        $def = new MachineDefine();
        $def->setMachineFactoryClass(
            $this->checkClassName(
                $parsedConfig->machineFactory,
                "状态机的工厂类",
                MachineFactory::class
            )
        );
        $def = $this->buildRoleList($def, $parsedConfig);
        $def = $this->buildActionList($def, $parsedConfig);
        return $this->buildNodeList($def, $parsedConfig);
    }

    /**
     * 检查转移类名
     * @param string $configClassName 配置文件中的类名
     * @param string $errorTip 类名用处，出错时的信息提示
     * @param string $interface 要求实现的接口
     * @return string 转换后的类名
     * @throws DefineConfigException
     */
    private function checkClassName(string $configClassName, string $errorTip, string $interface): string
    {
        $class = str_replace(".", "\\", $configClassName);
        if (!class_exists($class)) {
            throw new DefineConfigException("{$errorTip} 使用的类不存在 {$class}");
        }
        if (!is_subclass_of($class, $interface)) {
            throw new DefineConfigException("{$errorTip} {$class} 未实现指定接口 {$interface}");
        }
        return $class;
    }

    /**
     * 构建角色列表
     * @param MachineDefine $machineDefine 状态机定义
     * @param stdClass $parsedConfig 解析后的配置
     * @return MachineDefine
     * @throws DefineConfigException
     */
    private function buildRoleList(MachineDefine $machineDefine, stdClass $parsedConfig): MachineDefine
    {
        foreach ($parsedConfig->roles as $role) {
            $detail = new MachineRoleDetail();
            $detail->setId($role->id)
                ->setName($role->name)
                ->setRoleType(RoleType::from($role->roleType));
            if ($machineDefine->addRole($detail)) {
                throw new DefineConfigException("存在重复的角色标识 {$detail->getId()}");
            }
        }
        return $machineDefine;
    }

    /**
     * 构建动作列表
     * @param MachineDefine $machineDefine 状态机定义
     * @param stdClass $parsedConfig 解析后的配置
     * @return MachineDefine
     * @throws DefineConfigException
     */
    private function buildActionList(MachineDefine $machineDefine, stdClass $parsedConfig): MachineDefine
    {
        foreach ($parsedConfig->actions as $action) {
            foreach ($action->operatorList as $oid) {
                if (!$machineDefine->getRole($oid)) {
                    throw new DefineConfigException("动作 {$action->key} 的操作员 {$oid} 未定义");
                }
            }
            $detail = new MachineActionDetail();
            $detail->setKey($action->key)
                ->setName($action->name)
                ->setOperatorList($action->operatorList);
            if (isset($action->precondition)) {
                $detail->setPrecondition(
                    $this->checkClassName(
                        $action->precondition,
                        "动作{$action->key}的前置条件类",
                        PreConditionInterface::class
                    )
                );
            }
            if (isset($action->statusQuery)) {
                $detail->setStatusQuery(
                    $this->checkClassName(
                        $action->statusQuery,
                        "动作{$action->key}的状态查询类",
                        ActionStatusQueryInterface::class
                    )
                );
            }
            if (isset($action->execCondition)) {
                $detail->setExecCondition(
                    $this->checkClassName(
                        $action->execCondition,
                        "动作{$action->key}的后置条件类",
                        ActionExecConditionInterface::class
                    )
                );
            }
            if ($machineDefine->addAction($detail)) {
                throw new DefineConfigException("存在重复的动作标识 {$detail->getKey()}");
            }
        }
        return $machineDefine;
    }

    /**
     * 构建节点列表
     * @param MachineDefine $machineDefine
     * @param stdClass $parsedConfig
     * @return MachineDefine
     * @throws DefineConfigException
     */
    private function buildNodeList(MachineDefine $machineDefine, stdClass $parsedConfig): MachineDefine
    {
        $actionList = $machineDefine->getActionList();
        $nodeMap = [];
        foreach ($parsedConfig->nodes as $node) {
            $nodeMap[$node->key] = true;
        }
        foreach ($parsedConfig->nodes as $node) {
            $nodeDetail = new MachineNodeDetail();
            $nodeDetail->setKey($node->key);
            $nodeDetail->setName($node->name);
            $nodeDetail->setMainStream($node->mainStream);
            if ($machineDefine->addNode($nodeDetail)) {
                throw new DefineConfigException("存在重复的节点标识 {$nodeDetail->getKey()}");
            }
            foreach ($node->transfer as $transfer) {
                if (!isset($actionList[$transfer->actionId])) {
                    throw new DefineConfigException("节点 {$node->key} 的状态转移动作标识 {$transfer->actionId} 不存在");
                }
                switch ($transfer->type) {
                    case TransferType::IMMEDIATELY:
                        $transferDetail = new ImmediatelyTransfer();
                        $transferDetail->setTarget($transfer->target);
                        break;
                    case TransferType::SWITCH_TRANS:
                    default:
                        $transferDetail = new SwitchTransfer();
                        $transferDetail->setCanPreJudge($transfer->switch->canPreJudge);
                        $transferDetail->setName($transfer->switch->name);
                        $transferDetail->setClass(
                            $this->checkClassName(
                                $transfer->switch->class,
                                "节点 {$node->key} 的状态转移 {$transfer->actionId} 的条件判断类",
                                TransferDecisionInterface::class
                            )
                        );
                        foreach ($transfer->switch->resultList as $result) {
                            if (!isset($nodeMap[$result->target])) {
                                throw new DefineConfigException(
                                    "节点 {$node->key} 的状态转移 {$transfer->actionId} "
                                    . "的目标节点 {$result->target} 不存在"
                                );
                            }
                            $resultDetail = new SwitchResult();
                            $resultDetail->setResult($result->result);
                            $resultDetail->setDesc($result->desc);
                            $resultDetail->setTarget($result->target);
                            if ($transferDetail->addResult($resultDetail)) {
                                throw new DefineConfigException(
                                    "节点 {$node->key} 的状态转移 "
                                    . "{$transfer->actionId} 有重复的条件判断"
                                );
                            }
                        }
                        break;
                }
                $transferDetail->setMainStream($transfer->mainStream);
                $transferDetail->setActionId($transfer->actionId);
                if ($nodeDetail->addTransferList($transferDetail)) {
                    throw new DefineConfigException("节点 {$node->key} 的状态转移有重复的动作标识 {$transfer->actionId}");
                }
            }
            # todo 处理operation
        }
        return $machineDefine;
    }
}

# end of file
